package com.jb.faces.el;

import com.jb.web.bean.Page;
import com.jb.web.bean.UIComponentImpl;
import com.jb.web.content.ContentManager;
import com.jb.web.context.WebContext;

import com.jb.web.session.Session;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.jsp.el.ELException;
import javax.servlet.jsp.el.VariableResolver;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public class JBVariableResolver implements VariableResolver {
    protected static Log log = LogFactory.getLog(JBVariableResolver.class.getName());

    //
    //extension can register name resolver to override default resolver
    //
    static private Map externalResolvers = new HashMap();
    private Object variable;
    private WebContext context;

    public JBVariableResolver(WebContext pCtx, Object pComponent) {
        variable = pComponent;
        context = pCtx;
    }

    /**
    * Registers an external name resolver.
    * @param resolver external name resolver.
    */
    public static void registerResolver(VariableResolver resolver) {
        externalResolvers.put(resolver, resolver);
    }

    /**
     * Resolves variable by name
     * @param pName name of the variable.
     * @return object if resolved
     * @throws ELException if failed to resolve the name.
     */
    public Object resolveVariable(String pName) throws ELException {
        if (log.isDebugEnabled()) {
            log.debug("resolveVariable " + pName);
        }

        //if there are external name resolvers, use them first
        if (!externalResolvers.isEmpty()) {
            Object returnObject = resolveExternal(pName);

            if (returnObject != null) {
                return returnObject;
            }
        }

        if ("context".equalsIgnoreCase(pName)) {
            return context;
        }

        Session session = context.getSession();
        if ("guid".equalsIgnoreCase(pName)) {
            return com.jb.web.model.GUID.getInstance();
        } else if ("cookies".equalsIgnoreCase(pName)) {
            return context.getCookies();
        } else if ("config".equalsIgnoreCase(pName)) {
            return com.jb.web.application.configuration.Config.getInstance();
        } else if ("session".equalsIgnoreCase(pName)) {
            return session;
        } else if ("sessionScope".equalsIgnoreCase(pName)) {
            return session.getAttributes();
        } else if ("requestScope".equalsIgnoreCase(pName)) {
            return context.getParameters();
        } else if ("request".equalsIgnoreCase(pName)) {
            return context.getRequest();
        } else if ("applicationScope".equalsIgnoreCase(pName)) {
            return session.getApplication().getAttributes();
        } else if ("application".equalsIgnoreCase(pName)) {
            return session.getApplication();
        } else if ("database".equalsIgnoreCase(pName)) {
            return context.getDatabase();
        } else if ("param".equalsIgnoreCase(pName)) {//deprecated
            return context.getParameters();
        } else if ("resource".equalsIgnoreCase(pName)) {
            return context.getResources();
        } else if ("content".equalsIgnoreCase(pName)) {
            return ContentManager.getInstance(context, false);
        } else if ("this".equalsIgnoreCase(pName)) {
            return variable;
        } else {
            if (context.getRequest().getParameter(pName) != null) {
                return context.getRequest().getParameter(pName);
            } else if (context.getParameter(pName) != null) {
                return context.getParameter(pName);
            } else if (session.getAttribute(pName) != null) {
                return session.getAttribute(pName);
            }  if (variable instanceof UIComponentImpl) {
                Object v= findComponentByClientId((UIComponentImpl) variable, pName);
                if(v==null)
                    throw new ELException("Failed to resolve variable " + pName + " for " +  this.variable);
                return v;
            } else {
                throw new ELException("Failed to resolve variable " + pName +  " for " +  this.variable);
            }
        }
    }

    private Object findComponentByClientId(UIComponentImpl pComp, String pName) {
        if (pComp == null) {
            return null;
        }

        Page page = (Page) pComp.getRoot();
        Object obj = page.getDescendantByClientId(pName);

        if (obj != null) {
            return obj;
        }

        //depends on its phase
        UIComponentImpl reqeustComp = (UIComponentImpl) context.getRequestTree();

        if (reqeustComp == null) {
            return null;
        }

        obj = reqeustComp.getRoot().getDescendantByClientId(pName);

        if (obj != null) {
            return obj;
        }

        reqeustComp = (UIComponentImpl) context.getRespondTree();

        if (reqeustComp == null) {
            return null;
        }

        obj = reqeustComp.getRoot().getDescendantByClientId(pName);

        if (obj != null) {
            return obj;
        }

        return null;
    }

    //resolve name using external name resolvers
    private Object resolveExternal(String name) throws ELException {
        Iterator iter = externalResolvers.values().iterator();

        while (iter.hasNext()) {
            VariableResolver resolver = (VariableResolver) iter.next();
            Object obj = resolver.resolveVariable(name);

            if (obj != null) {
                return obj;
            }
        }

        return null;
    }
}
